#include "../pch.h"
#include "REFFile.h"

#ifndef	_WIN32
#include <fcntl.h>
#include <errno.h>
#endif

namespace BFX
{

//
// Constructions
//
REFFile::REFFile()
	: m_hFile(0/*NULL*/), m_bCanRead(false), m_bCanWrite(false), m_bCanSeek(false)
{
}

REFFile::REFFile(const String& strFileName, FileMode mode, FileAccess access)
	: m_hFile(0/*NULL*/), m_bCanRead(false), m_bCanWrite(false), m_bCanSeek(false)
{
	Open(strFileName, mode, access);
}

REFFile::~REFFile()
{
	Close();
}

//
// Operations
//

// Opens a file with given name and flag.
bool REFFile::Open(const String& strFileName, FileMode mode, FileAccess access)
{
	BFX_REQUIRE1(!strFileName.IsEmpty(), "Empty path name is not legal.");

	bool bSeekToEnd = (mode == FileMode_Append) ? true : false;

#ifdef _WIN32
	int nAccess = (access == FileAccess_Read) ? GENERIC_READ : (access == FileAccess_Write) ? GENERIC_WRITE : GENERIC_READ | GENERIC_WRITE;
	m_hFile = CreateFile(strFileName, (DWORD) nAccess, FILE_SHARE_READ, NULL, (DWORD) mode, 0, NULL);
	if (m_hFile == INVALID_HANDLE_VALUE)
		m_hFile = NULL;
#else	// _WIN32
	int nAccess = (access == FileAccess_Read) ? O_RDONLY : (access == FileAccess_Write) ? O_WRONLY : O_RDWR;
	int nMode = 0;
	if (mode == FileMode_Create || mode == FileMode_Append)
		nMode |= O_CREAT;
	if (mode == FileMode_Truncate)
		nMode |= O_TRUNC;
	m_hFile = open(strFileName, nAccess, nMode);
#endif	// _WIN32

	// For Append mode...
	if (IsValid())
	{
		m_strFileName = strFileName; // TODO: normalize file path.
		m_bCanRead = (access & FileAccess_Read) != 0;
		m_bCanWrite = (access & FileAccess_Write) != 0;
		m_bCanSeek = true;
		if (bSeekToEnd)
		{
			SeekToEnd();
		}
		return true;
	}

	return false;
}

// Gets a value indicating whether the current stream supports reading.
bool REFFile::CanRead() const
{
	return m_bCanRead;
}
// Gets a value indicating whether the current stream supports writing.
bool REFFile::CanWrite() const
{
	return m_bCanWrite;
}
// Gets a value indicating whether the current stream supports seeking.
bool REFFile::CanSeek() const
{
	return m_bCanSeek;
}

// Reads data into a buffer from the file.
int REFFile::Read(BYTE* pBuf, int nCount)
{
	BFX_REQUIRE1(pBuf, "Buffer cannot be null.");
	BFX_REQUIRE1(nCount >= 0, "Non-negative number required.");
	BFX_REQUIRE1(IsValid(), "Cannot access a closed file.");
	BFX_REQUIRE1(CanRead(), "File does not support reading.");

	int nBytesRead;
#ifdef	_WIN32
	if (!ReadFile(m_hFile, pBuf, nCount, (DWORD*) &nBytesRead, NULL))
		nBytesRead = -1;
	int nLastError = (int) GetLastError();
#else
	nBytesRead = read((int)m_hFile, pBuf, (size_t) nCount);
	int nLastError = errno;
#endif
	BFX_ENSURE1(nBytesRead >= 0, (PCSTR)String::Format("Failed to read file, error code: %d", nLastError));

	return nBytesRead;
}

// Writes data from a buffer to the file.
void REFFile::Write(const BYTE* pBuf, int nCount)
{
	BFX_REQUIRE1(pBuf, "Buffer cannot be null.");
	BFX_REQUIRE1(nCount >= 0, "Non-negative number required.");
	BFX_REQUIRE1(IsValid(), "Cannot access a closed file.");
	BFX_REQUIRE1(CanWrite(), "File does not support writing.");

	int nBytesWrite;
#ifdef	_WIN32
	if (!WriteFile(m_hFile, pBuf, nCount, (DWORD*) &nBytesWrite, NULL))
		nBytesWrite = -1;
	int nLastError = (int) GetLastError();
#else
	nBytesWrite = write((int)m_hFile, pBuf, nCount);
	int nLastError = errno;
#endif

	BFX_ENSURE1(nBytesWrite >= 0, (PCSTR)String::Format("Failed to write file, error code: %d", nLastError));
}

// Repositions the pointer in a previously opened file.
INT64 REFFile::Seek(INT64 lOff, SeekOrigin origin)
{
	BFX_REQUIRE1(IsValid(), "Cannot access a closed file.");
	BFX_REQUIRE1(CanSeek(), "File does not support seeking.");

	INT64 lRet;
#ifdef	_WIN32
	int low = (int) lOff;
	int high = (int) (lOff >> 32);
	low = SetFilePointer(m_hFile, low, (long*) &high, (DWORD) origin);
	lRet = low == -1 ? -1 : ((INT64) high << 32 | (UINT32) low);
	int nLastError = (int) GetLastError();
#else
	lRet = lseek((int)m_hFile, lOff, origin);
	int nLastError = errno;
#endif
	BFX_ENSURE1(lRet >= 0, (PCSTR)String::Format("Failed to seek file to the offset, error code: %d", nLastError));

	return lRet;
}

// Sets the value of the file pointer to the beginning of the file.
void REFFile::SeekToBegin()
{
	Seek(0, SeekOrigin_Begin);
}

// Sets the value of the file pointer to the logical end of the file.
INT64 REFFile::SeekToEnd()
{
	return Seek(0, SeekOrigin_End);
}

// Obtains the current logical length of the file in bytes.
INT64 REFFile::GetLength() const
{
	BFX_REQUIRE1(IsValid(), "Cannot access a closed file.");
	BFX_REQUIRE1(CanSeek(), "File does not support seeking.");

	INT64 lRet;
#ifdef	_WIN32
	int high, low;
	low = (int) GetFileSize(m_hFile, (DWORD*) &high);
	lRet = low == -1 ? -1 : ((INT64) high << 32 | (UINT32) low);
	int nLastError = (int) GetLastError();
	BFX_ENSURE1(lRet >= 0, String::Format("Failed to get length of file', error code: %d", nLastError));
#else
	// In the POSIX system, I found no better way to get the file size.
	// So, just seek to the end position to get the size, then seek back to previous position.
	REFFile* pThis = const_cast<REFFile*>(this);
	INT64 lCur = pThis->Seek(0L, SeekOrigin_Current);
	lRet = pThis->Seek(0L, SeekOrigin_End);
	pThis->Seek(lCur, SeekOrigin_Begin);
#endif
	return lRet;
}

// Call this member function to retrieve the full path of a specified file.
String REFFile::GetFilePath() const
{
	return m_strFileName;
}

// Obtains the current value of the file pointer, which can be used in subsequent calls to Seek.
INT64 REFFile::GetPosition() const
{
	BFX_REQUIRE1(IsValid(), "Cannot access a closed file.");
	BFX_REQUIRE1(CanSeek(), "File does not support seeking.");

	INT64 lRet;
#ifdef	_WIN32
	int high = 0;
	int low = 0;
	low = SetFilePointer(m_hFile, low, (long*) &high, SeekOrigin_Current);
	lRet = low == -1 ? -1 : ((INT64)high << 32 | (UINT32) low);
#else
	lRet = lseek((int)m_hFile, 0, SeekOrigin_Current);
#endif
	return lRet;
}

// Call this function to change the length of the file.
void REFFile::SetLength(INT64 nNewLen)
{
	BFX_REQUIRE1(nNewLen >= 0, "Non-negative number required.");
	BFX_REQUIRE1(IsValid(), "Cannot access a closed file.");
	BFX_REQUIRE1(CanSeek(), "File does not support seeking.");

	Seek(nNewLen, SeekOrigin_Begin);
}

// Closes the file associated with this object and makes the file unavailable for reading or writing.
void REFFile::Close()
{
	if (IsValid())
	{
#ifdef	_WIN32
		CloseHandle(m_hFile);
#else
		close((int)m_hFile);
#endif
	}

	m_hFile = 0/*NULL*/;
	m_bCanRead = false;
	m_bCanWrite = false;
	m_bCanSeek = false;
}

//
// Static operations
//

// This static function deletes the file specified by the path.
bool REFFile::Remove(const String& strFileName)
{
	BFX_ASSERT(!strFileName.IsEmpty());

	bool bRetVal;
#ifdef	_WIN32
	bRetVal = (TRUE == DeleteFile(strFileName));
#else
	bRetVal = (0 == remove(strFileName));
#endif
	return bRetVal;
}

// This static function renames the specified file.
bool REFFile::Rename(const String& strOldFileName, const String& strNewFileName)
{
	BFX_ASSERT(!strOldFileName.IsEmpty());
	BFX_ASSERT(!strNewFileName.IsEmpty());

	bool bRetVal;
#ifdef	_WIN32
	bRetVal = (TRUE == MoveFile(strOldFileName, strNewFileName));
#else
	bRetVal = (0 == rename(strOldFileName, strNewFileName));
#endif
	return bRetVal;
}

bool REFFile::IsValid() const
{
	return (m_hFile != 0/*NULL*/);
}

}	//	namespace BFX
